<?php

namespace App\Models\Invoice\Platform;

use App\Models\Invoice\GoodDetail;
use App\Models\Invoice\Subject;
use App\Models\ParkingLot\MthOrder;
use App\Models\ParkingLot\ParkOrder;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Support\Arr;

/**
 * https://mobl-test.chinaums.com/fapiao-test/test/index.do
 */
class ChinaUms
{
    const URL_DEV = 'https://mobl-test.chinaums.com/fapiao-api-test/';
    const URL = '';
    const DESCRIPTION = '银联商务:';
    const NOTIFY_URL = '/api/payment/invoice/notify/' . Subject::TYPE_CHINA_UMS . '/';
    const TIMEOUT = 120;

    protected $url;
    protected $name;
    protected $key;
    protected $msgSrc;
    protected $merchantId;
    protected $terminalId;
    protected $msgType;
    protected $msgId;
    protected $srcReserve;
    protected $requestTimestamp;
    protected $logger;
    protected $queryBody = [];
    protected $cancel = false;
    protected $errors = [];
    protected $subject;
    protected $goodDetails;
    protected $parkingLotID;

    const MATERIAL_PAPER = 'PAPER';
    const MATERIAL_ELECTRONIC = 'ELECTRONIC';

    const TYPE_PLAIN = 'PLAIN';
    const TYPE_VAT = 'VAT';

    const STATUS_ISSUED = 'ISSUED';
    const STATUS_ISSUING = 'ISSUING';

    const STATUS_REVERSING = 'REVERSING';
    const STATUS_REVERSED = 'REVERSED';

    public function __construct(Subject $subject, bool $dev = true)
    {
        if ($dev) {
            $this->url = self::URL_DEV;
        } else {
            $this->url = self::URL;
        }

        $parameters = $subject->parameters ?: [];

        $this->msgSrc = \Arr::get($parameters, 'msgSrc');
        $this->merchantId = \Arr::get($parameters, 'merchantId');
        $this->terminalId = \Arr::get($parameters, 'terminalId');
        $this->key = \Arr::get($parameters, 'key');
        $this->parkingLotID = $subject->parking_lot_id;

        $this->goodDetails = GoodDetail::where('parking_lot_id', $subject->parking_lot_id)->get()->keyBy('category');

        $this->configValidator();
        $this->logger = \Log::channel('invoice');
    }

    protected function configValidator()
    {
        if (!$this->msgSrc) {
            throw new \Exception('消息来源不得为空');
        }

        if (!$this->key) {
            throw new \Exception('签名密钥不得为空');
        }

        if (!$this->merchantId) {
            throw new \Exception('测试商户号不得为空');
        }

        if (!$this->terminalId) {
            throw new \Exception('测试终端号不得为空');
        }
    }

    protected static function getMsgID(): string
    {
        return intval(microtime(true) * 1000);
    }

    protected static function timestamp(): string
    {
        return now()->format('YmdHis');
    }

    public function fmtTime($timeString): string
    {
        $dateTime = '';
        try {
            $dateTime = Carbon::parse($timeString)->format('Ymd');
        } catch (\Throwable $exception) {
            $this->cancel = true;
            $this->errors[] = '开票订单日期格式化错误';
        }
        return $dateTime;
    }

    protected static function getNotifyUrl(string $act): string
    {
        if (!$host = config('app.url')) {
            throw new \Exception('未配置后台域名');
        }
        $host = trim($host, '/');
        return $host . self::NOTIFY_URL . $act;
    }

    public function autoInsertWxCardPack(array $arguments = []): self
    {
        $subject = $this->subject;
        $this->name = '自动插入微信卡包';
        $this->msgType = 'weixin.card.auto.insert';

        if (!$merOrderDate = Arr::get($arguments, 'paid_at')) {
            $this->cancel = true;
            $this->errors[] = '开票订单日期不得为空';
        } else {
            $merOrderDate = $this->fmtTime($merOrderDate);
        }

        if (!$merOrderId = Arr::get($arguments, 'order_code')) {
            $this->cancel = true;
            $this->errors[] = '开票订单号不得为空';
        }

        $merWxAppId = Arr::get($arguments, 'merWxAppId') ?: Arr::get($subject, 'merWxAppId');
        $merWxOrderId = Arr::get($arguments, 'merWxAppId') ?: Arr::get($subject, 'merWxOrderId');

        if (!$merWxAppId) {
            $this->cancel = true;
            $this->errors[] = '商户微信AppId不得为空';
        }

        if (!$merWxOrderId) {
            $this->cancel = true;
            $this->errors[] = 'merWxOrderId不得为空';
        }

        $rawBody = compact('merOrderId', 'merOrderDate', 'merWxAppId', 'merWxOrderId');

        $this->commonInjection();
        $this->bodyInjection($rawBody);
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }


    public function heartBeat(array $arguments = []): self
    {
        $this->name = '心跳';
        $this->msgType = 'heartbeat';

        $this->commonInjection();
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }

    public function merchantStatus(array $arguments = []): self
    {
        $this->name = '商户开通状态查询';
        $this->msgType = 'mer.openStatus.query';

        $this->commonInjection();
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }

    public function getWxCardPackAuthUrl(array $arguments = []): self
    {
        $this->name = '获取微信插卡授权链接';
        $this->msgType = 'weixin.card';

        if (!$merOrderDate = Arr::get($arguments, 'paid_at')) {
            $this->cancel = true;
            $this->errors[] = '开票订单日期不得为空';
        } else {
            $merOrderDate = $this->fmtTime($merOrderDate);
        }

        if (!$merOrderId = Arr::get($arguments, 'order_code')) {
            $this->cancel = true;
            $this->errors[] = '开票订单号不得为空';
        }

        $rawBody = [
            'merOrderId' => $merOrderId,
            'merOrderDate' => $merOrderDate
        ];

        $this->commonInjection();
        $this->bodyInjection($rawBody);
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }

    public function reverse(array $arguments = []): self
    {
        $this->name = '红冲发票';
        $this->msgType = 'reverse';

        if (!$merOrderDate = Arr::get($arguments, 'paid_at')) {
            $this->cancel = true;
            $this->errors[] = '开票订单日期不得为空';
        } else {
            $merOrderDate = $this->fmtTime($merOrderDate);
        }

        if (!$merOrderId = Arr::get($arguments, 'order_code')) {
            $this->cancel = true;
            $this->errors[] = '开票订单号不得为空';
        }

        $rawBody = [
            'merOrderId' => $merOrderId,
            'merOrderDate' => $merOrderDate
        ];

        if ($redNotificationNo = strval(Arr::get($arguments, 'redNotificationNo'))) {
            $rawBody['redNotificationNo'] = $redNotificationNo;
        }

        $this->commonInjection();
        $this->bodyInjection($rawBody);
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }

    public function query(array $arguments = []): self
    {
        $this->name = '发票查询';
        $this->msgType = 'query';

        if (!$merOrderDate = Arr::get($arguments, 'paid_at')) {
            $this->cancel = true;
            $this->errors[] = '开票订单日期不得为空';
        } else {
            $merOrderDate = $this->fmtTime($merOrderDate);
        }

        if (!$merOrderId = Arr::get($arguments, 'order_code')) {
            $this->cancel = true;
            $this->errors[] = '开票订单号不得为空';
        }

        $rawBody = [
            'merOrderId' => $merOrderId,
            'merOrderDate' => $merOrderDate
        ];
        $this->commonInjection();
        $this->bodyInjection($rawBody);
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }

    /**
     * @param array $arguments
     * int $act
     *  - 0 直接开票
     *  - 1 生成开票二维码
     * @return $this
     * @throws \Exception
     */
    public function issuance(array $arguments = []): self
    {
        $subject = $this->subject;

        if (!$merOrderDate = Arr::get($arguments, 'paid_at')) {
            $this->cancel = true;
            $this->errors[] = '开票订单日期不得为空';
        } else {
            $merOrderDate = $this->fmtTime($merOrderDate);
        }

        if (!$merOrderId = Arr::get($arguments, 'order_code')) {
            $this->cancel = true;
            $this->errors[] = '开票订单号不得为空';
        }

        if (!$buyerName = strval(Arr::get($arguments, 'buyerName'))) {
            $this->cancel = true;
            $this->errors[] = '买方名称不得为空';
        }

        if (!$amount = intval(Arr::get($arguments, 'amount'))) {
            $this->cancel = true;
            $this->errors[] = '含税总金额不得为0';
        }

        $rawBody = [
            'srcReserve' => $this->parkingLotID,
            'invoiceMaterial' => Arr::get($arguments, 'material') ?: self::MATERIAL_ELECTRONIC,
            'invoiceType' => Arr::get($arguments, 'type') ?: self::TYPE_PLAIN,
            'merOrderDate' => $merOrderDate,
            'merOrderId' => $merOrderId,
            'buyerName' => $buyerName,
            'buyerTaxCode' => strval(Arr::get($arguments, 'buyerTaxCode')),
            'buyerAddress' => strval(Arr::get($arguments, 'buyerAddress')),
            'buyerTelephone' => strval(Arr::get($arguments, 'buyerTelephone')),
            'buyerBank' => strval(Arr::get($arguments, 'buyerBank')),
            'buyerAccount' => strval(Arr::get($arguments, 'buyerAccount')),
            'buyerBrevityCode' => strval(Arr::get($arguments, 'buyerBrevityCode')),
            'amount' => $amount,
            'deductionAmount' => intval(Arr::get($arguments, 'deductionAmount')),
            'remark' => strval(Arr::get($arguments, 'remark')),
            'notifyUrl' => self::getNotifyUrl(__FUNCTION__),
            'merWxAppId' => Arr::get($subject, 'merWxAppId'),
            'merWxOrderId' => Arr::get($subject, 'merWxOrderId'),
            'alipayUserId' => Arr::get($subject, 'alipayUserId'),
            'payee' => Arr::get($subject, 'payee'),
            'checker' => Arr::get($subject, 'checker'),
            'drawer' => Arr::get($subject, 'drawer'),
            'outTradeNo' => strval(Arr::get($arguments, 'outTradeNo')),
            'notFaceRemark' => strval(Arr::get($arguments, 'notFaceRemark')),
            'specialInvoiceFlag' => '00',
            'goodsDetail' => @json_encode(Arr::get($arguments, 'goodsDetail') ?: [])
        ];

        switch (\Arr::get($arguments, 'act')) {
            case 1:
                $this->name = '生成开票二维码';
                $this->msgType = 'get.qrcode';
                $rawBody['receiveMobileNo'] = strval(Arr::get($arguments, 'mobile'));
                $rawBody['receiveEmail'] = strval(Arr::get($arguments, 'eMail'));
                $rawBody['expireTime'] = 30;
                break;
            case 0:
            default:
                $this->name = '直接开票';
                $this->msgType = 'issue';
                $rawBody['notifyMobileNo'] = strval(Arr::get($arguments, 'mobile'));
                $rawBody['notifyEMail'] = strval(Arr::get($arguments, 'eMail'));
                break;
        }

        $this->commonInjection();
        $this->bodyInjection($rawBody);
        $this->queryBody['sign'] = $this->signGenerator($this->queryBody);
        return $this;
    }

    protected function commonInjection(): self
    {
        $this->msgId = self::getMsgID();
        $this->requestTimestamp = self::timestamp();

        $this->queryBody = array_merge($this->queryBody, [
            'msgId' => $this->msgId,
            'msgSrc' => $this->msgSrc,
            'requestTimestamp' => $this->requestTimestamp,
            'merchantId' => $this->merchantId,
            'terminalId' => $this->terminalId,
            'msgType' => $this->msgType
        ]);
        return $this;
    }

    protected function bodyInjection(array $rawBody = []): self
    {
        $this->queryBody = array_merge($this->queryBody, $rawBody);
        return $this;
    }

    public function signGenerator(array $toBeEncryptedArr): string
    {
        $appendStr = joinStrWithoutNull($toBeEncryptedArr);
        $appendStr .= $this->key;
        return strtoupper(hash("sha256", $appendStr));
    }

    protected function cleanup()
    {
        $this->name = '';
        $this->msgType = '';
        $this->errors = [];
        $this->cancel = false;
        $this->queryBody = [];
        $this->msgId = '';
        $this->requestTimestamp = '';
    }

    public function fire()
    {
        $respBody = [];

        if ($this->cancel) {
            $errBox = $this->errors;
            if (is_array($errBox)) {
                $errMsg = implode(',', $errBox);
            } else {
                $errMsg = '未知错误';
            }
            throw new \Exception($errMsg);
        }

        $logPacket = ['url' => $this->url, 'queryBody' => $this->queryBody];
        $httpClient = new Client(['timeout' => self::TIMEOUT, 'verify' => false]);
        try {
            $respObj = $httpClient->request('POST', $this->url, ['json' => $this->queryBody]);
            $respBodyContents = $respObj->getBody()->getContents();
            $respBody = @json_decode($respBodyContents, true) ?: [];
            $logPacket['response'] = $respBody ?: $respBodyContents;
        } catch (\Throwable $exception) {
            $logPacket['err'] = $exception->getMessage();
        } finally {
            $this->logger && $this->logger->info(self::DESCRIPTION . $this->name, $logPacket);
            if (isset($exception)) {
                throw $exception;
            }
        }
        return $respBody;
    }

    public function getGoodDetails($order): array
    {
        $invoiceGoodsDetail = [];
        $goodDetails = $this->goodDetails;

        if ($order instanceof ParkOrder) {
            //order_title ,total_amount,coupon_amount,final_amount
            if (!$goodDetail = $goodDetails->get(GoodDetail::CATEGORY_TMP_PAYMENT)) {
                throw new \Exception('未找到对应的开票商品');
            }
            $totalAmount = floatval($order->total_amount / 100);
            $orderTitle = $order->order_title;
            $taxRate = intval($goodDetail->tax_rate);
            $sn = strval($goodDetail->sn);
            $couponAmount = floatval(abs($order->coupon_amount) / 100);
        } elseif ($order instanceof MthOrder) {

            if (!$goodDetail = $goodDetails->get(GoodDetail::CATEGORY_MONTHLY_PAYMENT)) {
                throw new \Exception('未找到对应的开票商品');
            }
            $totalAmount = floatval($order->total_amount / 100);
            $finalAmount = floatval($order->final_amount / 100);
            $orderTitle = $order->order_title;
            $taxRate = intval($goodDetail->tax_rate);
            $sn = strval($goodDetail->sn);
            $couponAmount = abs($totalAmount - $finalAmount);
        } else {
            throw new \Exception('仅支持临停和包月订单生成');
        }

        if ($totalAmount) {
            if ($couponAmount) {
                $invoiceGoodsDetail = [
                    [
                        'index' => 1,
                        'attribute' => 1,
                        'discountIndex' => 2,
                        'name' => $orderTitle,
                        'sn' => $sn,
                        'taxRate' => $taxRate,
                        'priceIncludingTax' => -$couponAmount
                    ],
                    [
                        'index' => 2,
                        'attribute' => 2,
                        'discountIndex' => 1,
                        'name' => $orderTitle,
                        'sn' => $sn,
                        'taxRate' => $taxRate,
                        'priceIncludingTax' => $totalAmount
                    ]
                ];
            } else {
                $invoiceGoodsDetail = [
                    [
                        'index' => 1,
                        'attribute' => 0,
                        'discountIndex' => '',
                        'name' => $orderTitle,
                        'sn' => $sn,
                        'taxRate' => $taxRate,
                        'priceIncludingTax' => $totalAmount
                    ]
                ];
            }
        }

        return $invoiceGoodsDetail;
    }
}
